import { upload, region as qiniuRegion, compressImage, QiniuError, QiniuRequestError } from 'qiniu-js'
import { fileSuffix, random } from './util'
import { $axios } from '@plugins/axios'
import { UploadProgress } from 'qiniu-js/esm/upload'

export interface QiniuUploadOption {
  /** 上传的文件 */
  file: File
  /** 上传文件名 */
  key?: string
  /** 自动生成 key 参数的规则名 */
  rule?: keyof typeof rules | ((file: File) => string)
  /** 上传自动重试次数（整体重试次数，而不是某个分片的重试次数）；默认 3 次（即上传失败后最多重试两次） */
  retryCount?: number
  /** 选择上传域名区域。可选值：z0、z1、z2、na0、as0 */
  region?: keyof typeof qiniuRegion
}

export interface QiniuUploadProgress {
  /** 上传的 key */
  key: string
  /** 上传的文件 */
  file: File
  /** 上传百分比 */
  percent: number
  /** 上传结束/终止 */
  done: boolean
}

export interface QiniuUploadResult {
  key: string
}

const Source = {
  token: '',
  hostname: '7file.5wudao.com',
}

const ErrorCode = {
  '298': '部分操作执行成功。',
  '400': '请求报文格式错误，包括上传时，上传表单格式错误。',
  '401': '认证授权失败，错误信息包括密钥信息不正确；数字签名错误；授权已超时。',
  '403': '权限不足，拒绝访问。',
  '404': '资源不存在，包括空间资源不存在；镜像源资源不存在。',
  '405': '请求方式错误，主要指非预期的请求方式。',
  '406': '上传的数据 CRC32 校验错误。',
  '413': '请求资源大小大于指定的最大值。',
  '419': '用户账号被冻结。',
  '478': '镜像回源失败，主要指镜像源服务器出现异常。',
  '502': '错误网关。',
  '503': '服务端不可用。',
  '504': '服务端操作超时。',
  '573': '单个资源访问频率过高。',
  '579': '上传成功但是回调失败，包括业务服务器异常；七牛服务器异常；服务器间网络异常。',
  '599': '服务端操作失败。',
  '608': '资源内容被修改。',
  '612': '指定资源不存在或已被删除。',
  '614': '目标资源已存在。',
  '630': '已创建的空间数量达到上限，无法创建新空间。',
  '631': '指定空间不存在。',
  '640': '调用列举资源(list)接口时，指定非法的marker参数。',
  '701': '在断点续上传过程中，后续上传接收地址不正确或ctx信息已过期。',
}

function token() {
  if (Source.token) {
    return Promise.resolve(Source.token)
  }
  return $axios.post('getQiNiuToken').then(({ data }) => {
    Source.token = data
    return data
  })
}

//////////////////////////////////////////////////// 上传进度 ////////////////////////////////////////////////////

type QiniuUploadInitialize = (key: string, cancel: Function) => void
type QiniuUploadPercent = (percent: number) => void
type QiniuUploadError = (error: Error) => void

interface QiniuUploadCallbacks {
  initialize: QiniuUploadInitialize
  percent: QiniuUploadPercent
  error?: QiniuUploadError
}

const UPLOAD_CALLBACK: WeakMap<File, QiniuUploadCallbacks> = new WeakMap()

export function qiniuUploadCallback(file: File, callbacks?: QiniuUploadCallbacks) {
  if (callbacks) {
    UPLOAD_CALLBACK.set(file, callbacks)
  } else {
    UPLOAD_CALLBACK.delete(file)
  }
}

//////////////////////////////////////////////////// 文件上传 ////////////////////////////////////////////////////

/**
 * 七牛云文件上传
 * @param {QiniuUploadOption} option
 */
export function qiniuUpload(option: QiniuUploadOption): Promise<QiniuUploadResult> {
  // 数据校验
  if (!(option.file instanceof File)) {
    return Promise.reject(new Error('您还未选择上传文件！！！'))
  }

  return new Promise((resolve, reject) => {
    let { key, region } = option
    const { file, retryCount, rule } = option

    // 七牛参数：key
    if (!key) {
      switch (typeof rule) {
        case 'string':
          key = rules[rule](file)
          break
        case 'function':
          key = rule(file)
          break
        default:
          key = file.name
          break
      }
    }

    // 上传域名区域
    if (!region || !Reflect.has(qiniuRegion, region)) {
      region = 'z2'
    }

    const putExtra = {
      fname: file.name,
      mimeType: file.type, // 指定所传的文件类型
    }

    const config = {
      useCdnDomain: true, // 使用 CDN 加速
      disableStatisticsReport: false, // 是否禁用日志报告，为布尔值，默认为 false
      retryCount: retryCount || 3, // 上传自动重试次数（整体重试次数，而不是某个分片的重试次数）；默认 3 次（即上传失败后最多重试两次）
      region: qiniuRegion[region], // 选择上传域名区域。
    }

    let rerequest = false

    function execute() {
      token().then((token) => {
        let callback = (percent: number) => {
          const option = UPLOAD_CALLBACK.get(file)
          if (option) {
            option.initialize(key ?? file.name, cancel)
            option.percent(percent)
            callback = (percent: number) => {
              UPLOAD_CALLBACK.get(file)?.percent(percent)
            }
          }
        }

        // 开始上传
        const subscription = upload(file, key, token, putExtra, config).subscribe({
          next(arg) {
            // 全局显示进度
            // ctx.$schedule(file, arg.total.percent)
            callback(arg.total.percent)
          },
          error: (error) => {
            let temp: Error = error
            if (error instanceof QiniuRequestError) {
              if (error.code === 401) {
                Source.token = ''
                if (!rerequest) {
                  rerequest = true
                  return execute()
                }
              }
              const message = Reflect.get(ErrorCode, error.code)
              if (message) {
                temp = new Error(message)
              }
            }
            UPLOAD_CALLBACK.get(file)?.error?.(error)
            reject(temp)
          },
          complete: resolve,
        })

        /**
         * 取消上传
         */
        function cancel() {
          subscription.unsubscribe()
        }
      })
    }

    execute()
  })
}

/**
 * 图片压缩（中图）
 * @param {File} file
 * @returns {Promise<File>}
 */
export function qiniuMiddle(file: File) {
  return compressImage(file, {
    quality: 1,
    maxWidth: 1000,
    noCompressIfLarger: true,
  }).then(({ dist }) => {
    return new File([dist], random(8), {
      type: file.type,
    })
  })
}

/**
 * 图片压缩（小图）
 * @param {File} file
 */
export function qiniuSmall(file: File) {
  return compressImage(file, {
    quality: 1,
    maxWidth: 640,
    noCompressIfLarger: true,
  }).then(({ dist }) => {
    return new File([dist], random(8), {
      type: file.type,
    })
  })
}

/**
 * 完整的预览链接
 * @param {String} name 文件名
 * @param {Boolean} https 是否添加 https 前缀
 */
export function qiniuFullLink(name: string, https = location.protocol === 'https:') {
  if (/(^https?|^data.+base64)/.test(name)) {
    return name
  }
  return `http${https ? 's' : ''}://${Source.hostname}/${name}`
}

//////////////////////////////////////////////////// 动态生成上传 key ////////////////////////////////////////////////////

const rules = {
  idolPicture(file: File) {
    return `idolBgImg/${random(10)}${random(10)}${fileSuffix(file)}`
  },
  idolBackground(file: File) {
    return `idolBackground/${random(10)}${random(10)}${fileSuffix(file)}`
  },
  music(file: File) {
    return `music/${random(10)}${random(10)}${fileSuffix(file)}`
  },
  musicPicture(file: File) {
    return `musicPicture/${random(10)}${random(10)}${fileSuffix(file)}`
  },
}
